/*
 * Copyright 2002-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.beans.factory.xml;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeanUtils;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
 * {@link NamespaceHandler}接口的默认实现类。根据映射文件中定义的映射，解析namespace URI到具体的实现类。<br/>
 * Default implementation of the {@link NamespaceHandler} interface. Resolves
 * namespace URIs to implementation classes based on the mappings contained in
 * mapping file.
 * 
 * <p>
 * 默认情况下，这个实现类寻找<code>META-INF/spring.handlers</code>这个映射文件，但使用
 * {@link #DefaultNamespaceHandlerResolver(ClassLoader, String)}这一构造方法可以更改这一点。<br/>
 * 
 * By default, this implementation looks for the mapping file at
 * <code>META-INF/spring.handlers</code>, but this can be changed using the
 * {@link #DefaultNamespaceHandlerResolver(ClassLoader, String)} constructor.
 * 
 * @author Rob Harrop
 * @author Juergen Hoeller
 * @since 2.0
 * @see NamespaceHandler
 * @see DefaultBeanDefinitionDocumentReader
 */
public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {

	/**
	 * The location to look for the mapping files. Can be present in multiple
	 * JAR files.
	 */
	public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

	/** Logger available to subclasses */
	protected final Log logger = LogFactory.getLog(getClass());

	/**
	 * Stores the mappings from namespace URI Strings to NamespaceHandler
	 * instances
	 */
	private Map handlerMappings;

	/**
	 * Create a new <code>DefaultNamespaceHandlerResolver</code> using the
	 * default mapping file location.
	 * <p>
	 * This constructor will result in the thread context ClassLoader being used
	 * to load resources.
	 * 
	 * @see #DEFAULT_HANDLER_MAPPINGS_LOCATION
	 */
	public DefaultNamespaceHandlerResolver() {
		this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
	}

	/**
	 * Create a new <code>DefaultNamespaceHandlerResolver</code> using the
	 * default mapping file location.
	 * 
	 * @param classLoader
	 *            the {@link ClassLoader} instance used to load mapping
	 *            resources (may be <code>null</code>, in which case the thread
	 *            context ClassLoader will be used)
	 * @see #DEFAULT_HANDLER_MAPPINGS_LOCATION
	 */
	public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
		this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
	}

	/**
	 * Create a new <code>DefaultNamespaceHandlerResolver</code> using the
	 * supplied mapping file location.
	 * 
	 * @param classLoader
	 *            the {@link ClassLoader} instance used to load mapping
	 *            resources (may be <code>null</code>, in which case the thread
	 *            context ClassLoader will be used)
	 * @param handlerMappingsLocation
	 *            the mapping file location
	 */
	public DefaultNamespaceHandlerResolver(ClassLoader classLoader,
			String handlerMappingsLocation) {
		Assert.notNull(handlerMappingsLocation,
				"Handler mappings location must not be null");
		ClassLoader classLoaderToUse = (classLoader != null ? classLoader : ClassUtils
				.getDefaultClassLoader());
		initHandlerMappings(classLoaderToUse, handlerMappingsLocation);
	}

	/**
	 * Load the namespace URI -> <code>NamespaceHandler</code> class mappings
	 * from the configured mapping file. Converts the class names into actual
	 * class instances and checks that they implement the
	 * <code>NamespaceHandler</code> interface. Pre-instantiates an instance of
	 * each <code>NamespaceHandler</code> and maps that instance to the
	 * corresponding namespace URI.
	 */
	private void initHandlerMappings(ClassLoader classLoader,
			String handlerMappingsLocation) {
		Properties mappings = loadMappings(classLoader, handlerMappingsLocation);
		if (logger.isDebugEnabled()) {
			logger.debug("Loaded mappings [" + mappings + "]");
		}
		this.handlerMappings = new HashMap(mappings.size());
		for (Enumeration en = mappings.propertyNames(); en.hasMoreElements();) {
			String namespaceUri = (String) en.nextElement();
			String className = mappings.getProperty(namespaceUri);
			try {
				Class handlerClass = ClassUtils.forName(className, classLoader);
				if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
					throw new IllegalArgumentException("Class [" + className
							+ "] does not implement the NamespaceHandler interface");
				}
				NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils
						.instantiateClass(handlerClass);
				namespaceHandler.init();
				this.handlerMappings.put(namespaceUri, namespaceHandler);
			} catch (ClassNotFoundException ex) {
				if (logger.isDebugEnabled()) {
					logger.debug("Ignoring namespace handler [" + className
							+ "]: handler class not found", ex);
				}
			} catch (LinkageError err) {
				if (logger.isWarnEnabled()) {
					logger.warn("Ignoring namespace handler [" + className
							+ "]: problem with handler class file or dependent class",
							err);
				}
			}
		}
	}

	private Properties loadMappings(ClassLoader classLoader,
			String handlerMappingsLocation) {
		try {
			return PropertiesLoaderUtils.loadAllProperties(handlerMappingsLocation,
					classLoader);
		} catch (IOException ex) {
			throw new IllegalStateException(
					"Unable to load NamespaceHandler mappings from location ["
							+ handlerMappingsLocation + "]. Root cause: " + ex);
		}
	}

	/**
	 * Locate the {@link NamespaceHandler} for the supplied namespace URI from
	 * the configured mappings.
	 * 
	 * @param namespaceUri
	 *            the relevant namespace URI
	 * @return the located {@link NamespaceHandler}, or <code>null</code> if
	 *         none found
	 */
	public NamespaceHandler resolve(String namespaceUri) {
		return (NamespaceHandler) this.handlerMappings.get(namespaceUri);
	}

}
